<template>
  <div
    class="page_root"
    :key="queryUpdateFlag"
    data-page-name="工单SOP维护"
    data-page-identification="h_3ghlb91jr8"
  >
    <a-spin :spinning="pageLoading.loading" :tip="pageLoading.tip">
      <!-- begin vid_iw1ymevsqe 自定义模板_jnn31on94e -->
      <!--          新建编辑工单SOP-->
      <a-modal
        :visible="sopState.sopModalVisible"
        :title="sopState.modalTitle"
        @ok="confirmSop"
        @cancel="cancelSop"
        centered
        destroy-on-close
        :ok-button-props="{ loading: sopState.okLoading }"
        width="100%"
        :mask-closable="false"
        :body-style="{
          maxHeight: '800px',
          overflowY: 'auto',
          paddingBottom: 0,
        }"
        :footer="sopState.isDetail ? null : undefined"
      >
        <a-form
          :model="sopState.sopModalForm"
          ref="sopFormRef"
          :rules="sopState.sopFormRole"
        >
          <div class="sop-modal">
            <a-row>
              <a-col :span="24">
                <div
                  :class="[
                    'sop-modal',
                    { 'readonly-wrapper': sopState.isDetail },
                  ]"
                >
                  <a-col :span="24">
                    <a-form-item
                      label="SOP名称"
                      name="sopName"
                      :rules="sopState.sopFormRole.sopName"
                    >
                      <a-input
                        v-model:value="sopState.sopModalForm.sopName"
                        placeholder="请输入"
                        maxlength="50"
                        show-count
                      />
                    </a-form-item>
                  </a-col>
                  <a-row>
                    <a-col :span="8">
                      <a-form-item
                        label="工单类型"
                        :rules="sopState.sopFormRole.workType"
                        name="workType"
                      >
                        <a-select
                          :options="sopState.workTypeList"
                          v-model:value="sopState.sopModalForm.workType"
                          :field-names="{ label: 'label', value: 'value' }"
                          placeholder="请选择"
                          @change="hanldeTypeChange(false)"
                          :filterOption="
                            (input, option) => {
                              return (
                                option.label
                                  .toLowerCase()
                                  .indexOf(input.toLowerCase()) >= 0
                              );
                            }
                          "
                        />
                      </a-form-item>
                    </a-col>
                    <a-col :span="8">
                      <a-form-item
                        label="监控场景"
                        :rules="sopState.sopFormRole.monitorScene"
                        name="monitorScene"
                      >
                        <a-select
                          @focus="getMonitorSceneList(false)"
                          :options="sopState.monitorSceneList"
                          v-model:value="sopState.sopModalForm.monitorScene"
                          placeholder="请选择"
                          show-search
                          :field-names="{ label: 'label', value: 'value' }"
                          :filterOption="
                            (input, option) => {
                              return (
                                option.label
                                  .toLowerCase()
                                  .indexOf(input.toLowerCase()) >= 0
                              );
                            }
                          "
                        />
                      </a-form-item>
                    </a-col>
                    <a-col :span="8">
                      <a-form-item label="所属业务">
                        <a-select
                          :options="[
                            {
                              label: '工单',
                              value: '工单',
                            },
                          ]"
                          v-model:value="sopState.sopModalForm.belongBusiness"
                          placeholder="请选择"
                          show-search
                          :field-names="{ label: 'label', value: 'value' }"
                          :filterOption="
                            (input, option) => {
                              return (
                                option.label
                                  .toLowerCase()
                                  .indexOf(input.toLowerCase()) >= 0
                              );
                            }
                          "
                        />
                      </a-form-item>
                    </a-col>
                  </a-row>
                </div>
              </a-col>

              <a-col :span="24">
                <div class="modal-sop-title">
                  <div>SOP步骤配置：</div>
                </div>
              </a-col>
              <a-col :span="24">
                <div class="flow-content">
                  <!-- 主体部分：左中右 -->
                  <div class="flow-container">
                    <div class="sidebar" v-if="!sopState.isDetail">
                      <div
                        v-for="(node, index) in presetNodes"
                        :key="node.type"
                        class="draggable-node"
                        :draggable="true"
                        @dragstart="onDragStart($event, node)"
                      >
                        <div
                          :class="[
                            { 'step-icon': index == 0 },
                            { 'end-icon': index == 1 },
                          ]"
                        ></div>
                        <div>{{ node.data.label }}</div>
                      </div>
                    </div>
                    <!-- 画布 -->
                    <div
                      class="flow-area"
                      @drop="onDrop"
                      @dragover="onDragOver"
                      ref="flowContainer"
                    >
                      <VueFlow
                        v-model:nodes="nodes"
                        v-model:edges="edges"
                        @node-click="onNodeClick"
                        @connect="onConnect"
                        :selectEdgesOnClick="true"
                      >
                        <template #edge-default="props">
                          <BaseEdge
                            :id="props.id"
                            :path="
                              getStraightPath({
                                sourceX: props.sourceX,
                                sourceY: props.sourceY,
                                targetX: props.targetX,
                                targetY: props.targetY,
                              })[0]
                            "
                            :label-x="(props.sourceX + props.targetX) / 2"
                            :label-y="(props.sourceY + props.targetY) / 2"
                            label-bg-style="fill: whitesmoke"
                          />
                          <foreignObject
                            :x="(props.sourceX + props.targetX) / 2 - 30"
                            :y="(props.sourceY + props.targetY) / 2 - 20"
                            width="20"
                            height="20"
                            class="edge-delete-fo"
                          >
                            <div
                              class="edge-delete-btn"
                              v-if="props.selected"
                              @click.stop="removeEdge(props.id)"
                            >
                              <DeleteOutlined
                                style="color: #8a94a5; font-size: 20px"
                              />
                            </div>
                          </foreignObject>
                        </template>

                        <!--                        自定义节点-->
                        <template #node-start="nodeProps">
                          <div class="custom-node">
                            <div class="handle">
                              <handle type="source" position="right" />
                            </div>
                            <div class="node-content">
                              {{ nodeProps.data.label }}
                            </div>
                          </div>
                        </template>
                        <template #node-input="nodeProps">
                          <div
                            class="node-hover-wrapper"
                            :class="{
                              'node-hover-active': hoverNodeId === nodeProps.id,
                            }"
                          >
                            <div
                              class="custom-node"
                              @mouseenter="hoverNodeId = nodeProps.id"
                              @mouseleave="hoverNodeId = null"
                            >
                              <handle type="target" position="left" />
                              <div
                                class="node-line"
                                :title="nodeProps.data.sopStep"
                                :style="getBacColor(nodeProps)"
                              >
                                {{
                                  nodeProps.data.sopStep || "点击编辑SOP步骤"
                                }}
                              </div>
                              <handle type="source" position="right" />
                              <div
                                class="node-delete-btn"
                                v-if="nodeProps.selected"
                                @click.stop="removeNode(nodeProps.id)"
                              >
                                <DeleteOutlined
                                  style="color: #8a94a5; font-size: 20px"
                                />
                              </div>
                            </div>
                          </div>
                        </template>

                        <template #node-output="nodeProps">
                          <div
                            class="node-hover-wrapper"
                            :class="{
                              'node-hover-active': hoverNodeId === nodeProps.id,
                            }"
                          >
                            <div
                              class="custom-node"
                              @mouseenter="hoverNodeId = nodeProps.id"
                              @mouseleave="hoverNodeId = null"
                            >
                              <div class="handle">
                                <handle type="target" position="left" />
                              </div>
                              <div class="node-content">
                                {{ nodeProps.data.label }}
                              </div>
                              <div
                                class="node-delete-btn"
                                v-if="nodeProps.selected"
                                @click.stop="removeNode(nodeProps.id)"
                              >
                                <DeleteOutlined
                                  style="color: #8a94a5; font-size: 20px"
                                />
                              </div>
                            </div>
                          </div>
                        </template>

                        <Background />
                        <Controls />
                      </VueFlow>
                    </div>
                  </div>
                </div>
              </a-col>
              <a-col :span="24">
                <div
                  :class="[
                    'sop-modal',
                    { 'readonly-wrapper': sopState.isDetail },
                  ]"
                >
                  <a-col :span="24" v-if="isShowForm">
                    <a-form :model="selectedNode">
                      <a-col :span="24">
                        <a-form-item
                          label="SOP步骤"
                          name="sopStep"
                          :rules="sopState.sopFormRole.sopStep"
                        >
                          <a-input
                            placeholder="请输入"
                            maxlength="100"
                            show-count
                            v-model:value="selectedNode.sopStep"
                          />
                        </a-form-item>
                      </a-col>
                      <a-col :span="24">
                        <a-form-item
                          label="解决角色"
                          name="solverRole"
                          :rules="sopState.sopFormRole.solverRole"
                        >
                          <a-select
                            allow-clear
                            v-model:value="selectedNode.solverRole"
                            show-search
                            :options="sopState.solverList"
                            placeholder="请选择"
                            :field-names="{ label: 'label', value: 'value' }"
                            :filter-option="
                              (input, option) => {
                                return (
                                  option.label
                                    .toLowerCase()
                                    .indexOf(input.toLowerCase()) >= 0
                                );
                              }
                            "
                          />
                        </a-form-item>
                      </a-col>
                    </a-form>
                  </a-col>
                  <a-col :span="24">
                    <a-form-item label="完成标准">
                      <QuillEditor
                        v-model:content="sopState.sopModalForm.finishStandard"
                        ref="finishQuillEditor"
                        :options="editorOption"
                        class="quill-editor"
                      />
                    </a-form-item>
                  </a-col>
                  <a-col :span="24">
                    <a-form-item label="审核标准">
                      <QuillEditor
                        v-model:content="sopState.sopModalForm.assessorStandard"
                        ref="assQuillEditor"
                        :options="editorOption"
                        class="quill-editor"
                      />
                    </a-form-item>
                  </a-col>
                  <div class="modal-title"></div>
                  <a-col :span="24">
                    <a-form-item label="附件模板">
                      <a-upload
                        v-model:file-list="
                          sopState.sopModalForm.attachmentTemplateList
                        "
                        :multiple="true"
                        :before-upload="handlerSopUpload"
                        @remove="handlerSopRemove"
                        accept=".xlsx, image/*,.docx,, .xls,.mp4"
                      >
                        <a-button type="primary">
                          <upload-outlined />
                          上传
                        </a-button>
                      </a-upload>
                      <span>支持格式：.xlsx, image/*,.docx,, .xls,.mp4</span>
                    </a-form-item>
                  </a-col>
                </div>
              </a-col>
            </a-row>
          </div>
        </a-form>
      </a-modal>
      <!--          新建编辑工单SOP-->
      <div class="sop-body">
        <div class="sop-top-search">
          <a-row :gutter="24">
            <a-col :span="6">
              <a-form-item
                label="SOP名称"
                :label-col="{ span: 5 }"
                :wrapper-col="{ span: 19 }"
              >
                <a-input v-model:value="sopState.filter.sopName" />
              </a-form-item>
            </a-col>
            <a-col :span="6">
              <a-form-item
                label="工单类型"
                :label-col="{ span: 5 }"
                :wrapper-col="{ span: 19 }"
              >
                <a-select
                  allow-clear
                  show-search
                  v-model:value="sopState.filter.workType"
                  :options="sopState.workTypeList"
                  placeholder="请选择"
                  :field-names="{ label: 'label', value: 'value' }"
                  :filterOption="
                    (input, option) => {
                      return (
                        option.label
                          .toLowerCase()
                          .indexOf(input.toLowerCase()) >= 0
                      );
                    }
                  "
                  @change="hanldeTypeChange(true)"
                />
              </a-form-item>
            </a-col>
            <a-col :span="6">
              <a-form-item
                label="监控场景"
                :label-col="{ span: 5 }"
                :wrapper-col="{ span: 19 }"
              >
                <a-select
                  allow-clear
                  show-search
                  v-model:value="sopState.filter.monitorScene"
                  :options="sopState.monitorSceneList"
                  placeholder="请选择"
                  :field-names="{ label: 'label', value: 'value' }"
                  :filterOption="
                    (input, option) => {
                      return (
                        option.label
                          .toLowerCase()
                          .indexOf(input.toLowerCase()) >= 0
                      );
                    }
                  "
                  @focus="getMonitorSceneList(true)"
                />
              </a-form-item>
            </a-col>
            <a-col
              :span="6"
              style="display: flex; justify-content: end; align-items: start"
            >
              <a-space :size="16">
                <a-button type="primary" @click="getSopData">查询</a-button>
                <a-button type="primary" @click="resetSopData" ghost
                  >重置</a-button
                >
              </a-space>
            </a-col>
          </a-row>
        </div>
        <div class="sop-content">
          <div class="sop-title">
            <div class="sop-title-left">工单SOP列表</div>
            <a-button type="primary" ghost @click="handleAddSop">新建</a-button>
          </div>
          <div class="sop-table-box">
            <a-table
              :columns="sopState.columns"
              :dataSource="sopState.dataSource"
              :pagination="false"
              :loading="sopState.tableLoading"
              bordered
              @resizeColumn="handleResizeColumn"
              :showSorterTooltip="false"
              ref="tableRef"
              :scroll="{ y: 600 }"
            >
              <template #bodyCell="{ record, text, column, index }">
                <template v-if="column.dataIndex === 'attachmentTemplateList'">
                  <!--                                  预览/下载-->
                  <div
                    v-for="(item, index) in record.attachmentTemplateList"
                    :key="index"
                  >
                    <a-dropdown>
                      <span
                        style="
                          overflow: hidden;
                          white-space: nowrap;
                          text-overflow: ellipsis;
                          display: block;
                        "
                        :title="item.name"
                      >
                        {{ item.name }}
                      </span>
                      <template #overlay>
                        <a-menu>
                          <!--                                                    <a-menu-item v-if="item.type.startsWith('image')">-->
                          <!--                                                        <a-button-->
                          <!--                                                            type="link"-->
                          <!--                                                            size="small"-->
                          <!--                                                            :disabled="!item.type.startsWith('image')"-->
                          <!--                                                            @click="viewImage(item.url)"-->
                          <!--                                                            >预览</a-button-->
                          <!--                                                        >-->
                          <!--                                                    </a-menu-item>-->
                          <a-menu-item>
                            <a-button
                              style="width: 100%"
                              type="link"
                              size="small"
                              @click="downLoadSopFile(item)"
                              >下载</a-button
                            >
                          </a-menu-item>
                        </a-menu>
                      </template>
                    </a-dropdown>
                  </div>
                  <!--                                  预览/下载-->
                </template>
                <template v-if="column.dataIndex === 'opration'">
                  <a-space>
                    <a-button
                      type="link"
                      size="small"
                      @click="handlerEditSop(record.sopId, false)"
                      >编辑</a-button
                    >
                    <a-button
                      type="link"
                      size="small"
                      @click="handleDetail(record.sopId)"
                      >详情</a-button
                    >
                    <a-popconfirm
                      title="确认删除吗?"
                      ok-text="确定"
                      cancel-text="取消"
                      @confirm="handlerDeleteSop(record.sopId)"
                      @cancel="cancel"
                    >
                      <a-button type="link" size="small">删除</a-button>
                    </a-popconfirm>
                  </a-space>
                </template>
              </template>
            </a-table>
          </div>
        </div>
      </div>
      <!-- end vid_iw1ymevsqe 自定义模板_jnn31on94e -->
    </a-spin>
  </div>
</template>
<script setup lang="tsx">
import {
  h,
  ref,
  reactive,
  toRaw,
  markRaw,
  watch,
  computed,
  onBeforeMount,
  onMounted,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
} from "vue";
import { useRoute, useRouter } from "vue-router";

const router = useRouter();
const route = useRoute();
import {
  get,
  post,
  put,
  del,
  request,
  requestWithHeaders,
} from "@/utils/http/Axios";
import {
  cloneDeep,
  localGet,
  merge,
  getDifferenceArray,
  traversalTreeStructure,
  checkCaseCondition,
  renderStringInTemplate,
  handleBreadcrumbJump,
  throttle,
  permission,
  getComponentParamsValue,
} from "@/utils/index";
import {
  isNullOrEmpty,
  isEmptyArr,
  isNullOrUnDef,
  isContained,
  isArray,
  isEmpty,
  isObject,
} from "@/utils/is";
import dayjs from "dayjs";
import { getPagePcChartsDataSourceConvert } from "@/utils/chart";
import { message, notification, Modal } from "ant-design-vue";
import {
  propertyFormatByNumber,
  propertyFormatByPercentage,
  propertyFormatByDate,
  parseSubFromDataList,
} from "@/utils/format";
import colorTheme from "@/design/custom-theme.json";

import UserTableRecords from "@/model/UserTableRecords";
import { getAppBaseUrl } from "@/utils/index";
import { sendToken } from "@/hooks/useIframeToken";
import { useSystemStore } from "@/store/modules/system";

const systemStore = useSystemStore();

// 获取传入的流程ID和组件参数
const props = defineProps({
  processInstanceId: {
    type: [String, Number, Boolean],
    default: undefined,
  },
});
const immediateWatch = (source, callback) => {
  watch(source, callback, { deep: true, immediate: true });
};

/* -------------------------- 属性定义 -------------------------- */

const setTimeOutList = [];
const pageLoading = reactive({
  loading: false,
  tip: undefined,
});
const authInfo = localGet("app-user-store");

// 处理校验
const setSubFormName = (parentInfo, name) => {
  return [parentInfo.parents, parentInfo.cycleIndex, name];
};

/* ========================== 属性定义 ========================== */

/* -------------------------- 元素数据引用 -------------------------- */

/* ========================== 元素数据引用 ========================== */

/* -------------------------- 页面数据 -------------------------- */

/* ========================== 页面数据 ========================== */

/* -------------------------- 响应 -------------------------- */

/* ========================== 响应 ========================== */

/* -------------------------- 函数 -------------------------- */

/* ========================== 函数 ========================== */

/* -------------------------- 页面参数 -------------------------- */

const pageParameter_state = reactive({
  processInstanceId: props.processInstanceId ?? route.query.processInstanceId,
});

/* ========================== 页面参数 ========================== */

/* -------------------------- 组件事件处理方法 -------------------------- */

// ---------------------------- 组件数据同步 ----------------------------

// ============================ 组件数据同步 ============================

/* ========================== 组件事件处理方法 ========================== */

/* -------------------------- 生命周期 -------------------------- */
// 用于参数变更后重新执行自定义指令
const queryUpdateFlag = ref(0);
onMounted(() => {});

onUnmounted(() => {
  // 销毁定时器
  setTimeOutList.forEach(({ repeat, timer }) => {
    repeat ? clearInterval(timer) : clearTimeout(timer);
  });
});

/* ========================== 生命周期 ========================== */

/* -------------------------- 数据关联处理 -------------------------- */

/* -------------------------- 表单数据同步 -------------------------- */
/* ========================== 表单数据同步 ========================== */

/* ========================== 数据关联处理 ========================== */

/* -------------------------- 不同组件自定义的处理逻辑 -------------------------- */

/* ========================== 不同组件自定义的处理逻辑 ========================== */

// ================================定制化逻辑========================================
import { nextTick } from "vue";
import { QuillEditor, Quill } from "@vueup/vue-quill";
import ImageUploader from "quill-image-uploader";
import "@vueup/vue-quill/dist/vue-quill.snow.css";

const titleConfig = [
  { Choice: " .ql-bold", title: "加粗" },
  { Choice: " .ql-italic", title: "斜体" },
  { Choice: " .ql-underline", title: "下划线" },
  { Choice: " .ql-strike", title: "删除线" },
  { Choice: " .ql-blockquote", title: "块引用" },
  { Choice: " .ql-code-block", title: "插入代码" },
  { Choice: " .ql-size", title: "字体大小" },
  { Choice: " .ql-size", title: "字体大小" },
  { Choice: " .ql-color", title: "字体颜色" },
  { Choice: " .ql-background", title: "字体背景颜色" },
  { Choice: ' .ql-list[value="ordered"]', title: "有序列表" },
  { Choice: ' .ql-list[value="bullet"]', title: "无序列表" },
  { Choice: ' .ql-header[value="1"]', title: "1级标题" },
  { Choice: ' .ql-header[value="2"]', title: "2级标题" },
  { Choice: " .ql-align", title: "对齐方式" },
  { Choice: " .ql-clean", title: "清除字体格式" },
  { Choice: " .ql-image", title: "图像" },
  { Choice: ' .ql-size .ql-picker-item[data-value="small"]', title: "小号" },
  { Choice: ' .ql-size .ql-picker-item[data-value="large"]', title: "大号" },
  { Choice: ' .ql-size .ql-picker-item[data-value="huge"]', title: "超大号" },
  { Choice: " .ql-size .ql-picker-item:nth-child(2)", title: "标准" },
  { Choice: " .ql-align .ql-picker-item:first-child", title: "居左对齐" },
  {
    Choice: ' .ql-align .ql-picker-item[data-value="center"]',
    title: "居中对齐",
  },
  {
    Choice: ' .ql-align .ql-picker-item[data-value="right"]',
    title: "居右对齐",
  },
  {
    Choice: ' .ql-align .ql-picker-item[data-value="justify"]',
    title: "两端对齐",
  },
];
// 注册图片上传模块
Quill.register("modules/imageUploader", ImageUploader);

// 将本地文件转换为URL
const getLocalImageURL = (file) => {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      resolve(e.target.result);
    };
    reader.readAsDataURL(file);
  });
};

// 表单数据

// Quill 编辑器选项
const editorOption = ref({
  modules: {
    // imageUploader: {
    //     upload: async file => {
    //         const localUrl = await getLocalImageURL(file)
    //         return localUrl // 返回本地图片的 URL 进行预览
    //     }
    // },
    toolbar: [
      ["bold", "italic", "underline", "strike"], // 加粗，斜体，下划线，删除线
      // ['blockquote', 'code-block'], // 引用，代码块
      // [{ header: 1 }, { header: 2 }], // 标题1，标题2
      [{ list: "ordered" }, { list: "bullet" }], // 有序列表，无序列表
      [{ size: ["small", false, "large", "huge"] }], // 文字大小
      [{ color: [] }, { background: [] }], // 字体颜色，背景颜色
      [{ align: [] }], // 对齐
      ["clean"], // 清除格式
      // ['image'] // 链接，图片，视频
    ],
    // ImageUploader: imageHandler
  },
});

// 将 dataURL 转换为 File 对象的辅助函数
const dataURLtoFile = (dataurl, filename) => {
  const arr = dataurl.split(",");
  const mime = arr[0].match(/:(.*?);/)[1];
  const bstr = atob(arr[1]);
  let n = bstr.length;
  const u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
};
// 上传API
const uploadFileAPI = async (file) => {
  const data = new FormData();
  data.append("file", file);

  return post("/file-storage/api/v1/file/upload", data)
    .then(({ data: { fullUrl } }) => {
      return { data: { fullUrl } };
    })
    .catch((error) => {
      console.error("上传图片出错:", error);
      throw error; // 抛出错误以供外部处理
    });
};

function initTitle() {
  // 选择所有的 ql-editor，并去除 placeholder
  document.querySelectorAll(".ql-editor").forEach((editor) => {
    editor.dataset.placeholder = "";
  });

  // 遍历 titleConfig 并处理多个富文本
  for (let item of titleConfig) {
    document.querySelectorAll(".ql-toolbar" + item.Choice).forEach((tip) => {
      tip.setAttribute("title", item.title);
    });
  }
}

//     富文本设置
const sopState = reactive({
  filter: {
    sopName: undefined,
    workType: undefined,
    monitorScene: undefined,
  },
  tableLoading: false,

  dataSource: [],
  // 工单类型list
  workTypeList: [],
  // 监控场景
  monitorSceneList: [],
  columns: [
    {
      title: "SOP名称",
      dataIndex: "sopName",
      align: "center",
      width: 150,
      ellipsis: true,
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "工单类型",
      dataIndex: "workType",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "监控场景",
      dataIndex: "monitorSceneValue",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "所属业务",
      dataIndex: "belongBusiness",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "附件模板",
      dataIndex: "attachmentTemplateList",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "创建时间",
      dataIndex: "createdTime",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "创建人",
      dataIndex: "createdBy",
      ellipsis: true,
      width: 120,
      align: "center",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
    {
      title: "操作",
      dataIndex: "opration",
      ellipsis: true,
      width: 120,
      align: "center",
      fixed: "right",
      customCell: ({ rowSpan }) => {
        return {
          rowSpan: rowSpan,
        };
      },
    },
  ],
  sopModalForm: {
    sopName: undefined, //sop名称
    finishStandard: undefined, //完成标准
    assessorStandard: undefined, //审核标准
    workType: undefined, //工单类型
    monitorScene: undefined, //监控场景
    belongBusiness: "工单", //所属业务
    attachmentTemplateList: [], //附件名称,
    flowChart: {},
  },
  solverList: [], //解决角色选择
  sopModalVisible: false,
  isDetail: false,
  modalTitle: "新建工单SOP",
  okLoading: false,
  sopFormRole: {
    sopName: [{ required: true, message: "请输入SOP名称", trigger: "blur" }],
    sopStep: [{ required: true, message: "请输入SOP步骤", trigger: "blur" }],
    solverRole: [
      { required: true, message: "请选择解决角色", trigger: "blur" },
    ],
    workType: [{ required: true, message: "请选择工单类型", trigger: "blur" }],
    monitorScene: [
      { required: true, message: "请选择监控场景", trigger: "blur" },
    ],
    attachmentTemplateList: [
      { required: true, message: "上传附件", trigger: "blur" },
    ],
  },
});
onMounted(() => {
  getSopData();
  getWorkType();
  getSolverList();
});

// 获取列表
function getSopData() {
  sopState.tableLoading = true;
  post("/solution-ppm-work-order/api/v1/work_order_sop/search", sopState.filter)
    .then((data) => {
      if (data.code !== 200) {
        message.error(data.message);
        return;
      }
      sopState.dataSource = groupSopData(data.data);
    })
    .finally(() => {
      sopState.tableLoading = false;
    });
}

// 获取工单类型字典值
function getWorkType() {
  get(
    "/solution-ppm-work-order/api/v1/work_order_sop/get_work_type_by_monitor_scene"
  ).then((data) => {
    if (data.code !== 200) {
      message.error(data.message);
      return;
    }
    sopState.workTypeList = data.data.map((item) => {
      return {
        label: item.itemName,
        value: item.itemName,
      };
    });
  });
}

// 获取监控场景list
function getMonitorSceneList(flag) {
  console.log(flag);
  const params = flag
    ? sopState.filter.workType
    : sopState.sopModalForm.workType;
  console.log(params, "params");
  get(
    `/solution-ppm/api/v2/built-in-system-ppm/dictionary/search/${params}`
  ).then((data) => {
    if (data.code !== 200) {
      message.error(data.message);
      return;
    }
    sopState.monitorSceneList = data.data.map((item) => {
      return {
        label: item.itemName,
        value: item.itemCode,
      };
    });
  });
}

// 解决角色list
function getSolverList() {
  get("/solution-ppm-work-order/api/v1/work_order_sop/template_role").then(
    (data) => {
      if (data.code !== 200) {
        message.error(data.message);
        return;
      }
      sopState.solverList = data.data;
    }
  );
}

const handleResizeColumn = (w, col, vid) => {
  col.width = w;
};

// 打开图片的函数
function viewImage(url) {
  window.open(url, "_blank"); // 在新窗口打开图片
}

// 下载文件的函数
function downLoadSopFile(item) {
  fetch(item.url)
    .then((response) => response.blob())
    .then((blob) => {
      const downloadLink = document.createElement("a");
      const url = URL.createObjectURL(blob);
      downloadLink.href = url;
      downloadLink.setAttribute("download", item.name);
      downloadLink.style.display = "none";
      document.body.appendChild(downloadLink);
      downloadLink.click();
      document.body.removeChild(downloadLink);

      // Release the object URL after the download
      URL.revokeObjectURL(url);
      pageLoading.loading = false;
    })
    .catch((err) => {
      console.error("Download failed", err);
    });
}

const isExpanded = ref(false); // 控制展开/折叠的状态
// 上传文件
// 父上传文件
function handlerSopUpload(file) {
  const data = new FormData();
  data.append("file", file);

  post("/file-storage/api/v1/file/upload", data)
    .then(({ data: { fullUrl } }) => {
      if (data) {
        // 新文件对象
        const newFile = {
          uid: file.uid,
          lastModified: file.lastModified,
          name: file.name,
          size: file.size,
          type: file.type,
          status: "done",
          url: fullUrl,
        };

        // 去重，保留一个副本
        sopState.sopModalForm.attachmentTemplateList = [
          // 保留已存在文件（去除重复）
          ...sopState.sopModalForm.attachmentTemplateList.filter(
            (item) => item.uid !== file.uid && item.name !== file.name
          ),
          // 添加新的文件
          newFile,
        ];
      }
    })
    .catch((e) => {
      message.error("上传失败" + e);
    });

  return false;
}

// 父移除文件
function handlerSopRemove(file) {
  sopState.sopModalForm.attachmentTemplateList =
    sopState.sopModalForm.attachmentTemplateList.filter(
      (item) => item.uid !== file.uid
    );
}

// 父上传文件
function resetSopData() {
  sopState.filter = {
    sopName: undefined,
    type: undefined,
    monitorScene: undefined,
  };
  getSopData();
}

//     新建sop
function handleAddSop() {
  sopState.modalTitle = "新建SOP";
  currentSopData.value = {};
  sopState.monitorSceneList = [];
  sopState.sopModalVisible = true;
  sopState.isDetail = false;
  nextTick(() => {
    initTitle();
  });
  nodes.value = [
    {
      id: "start-1",
      type: "start",
      data: { label: "开始" },
      position: { x: 50, y: 100 },
      sourcePosition: Position.Right,
    },
  ];
}

const sopFormRef = ref(null);
const finishQuillEditor = ref(null);
const assQuillEditor = ref(null);
const currentSopData = ref({});

function groupSopData(data) {
  let currentSopName = "";

  // 遍历数据，计算每一行的 rowSpan
  return data.map((item, index) => {
    if (currentSopName !== item.sopName) {
      // 更新当前的标识符
      currentSopName = item.sopName;

      // 计算这个组的 rowSpan
      let rowSpan = 0;
      for (let i = index; i < data.length; i++) {
        const currentItem = data[i];
        if (currentSopName === currentItem.sopName) {
          rowSpan += 1;
        } else {
          break;
        }
      }
      item.rowSpan = rowSpan;
    } else {
      item.rowSpan = 0; // 如果是相同的组，重置 rowSpan
    }

    return item;
  });
}

function validateInputSteps(data) {
  console.log(data);

  for (const item of data) {
    if (
      item.type === "input" &&
      (!item.data.sopStep || !item.data.solverRole)
    ) {
      // 只要有一个不符合就返回 false
      return false;
    }
  }
  return true;
}

//     保存sop
function confirmSop() {
  sopFormRef.value.validate().then(async () => {
    if (nodes.value.length == 0 || edges.value.length == 0) {
      message.info("请补充SOP步骤");
      return;
    }
    if (!validateFlowChart(nodes.value, edges.value)) {
      message.warning("存在未连接的流程步骤，请检查流程图");
      return;
    }
    if (!validateInputSteps(nodes.value)) {
      // 替换成你的 UI 提示方式
      message.info("请补充SOP步骤");
      return;
    }

    sopState.okLoading = true;
    const finishDeltaContent = finishQuillEditor.value.getContents();
    const assDeltaContent = assQuillEditor.value.getContents();

    const imageUploadPromises = [];

    finishDeltaContent.ops.forEach((op) => {
      if (op.insert && op.insert.image && op.insert.image.startsWith("data:")) {
        const localSrc = op.insert.image;

        // 如果是 base64 图片，上传并替换为 URL
        const file = dataURLtoFile(localSrc, "image.png"); // 将 base64 转换为 File 对象

        const uploadPromise = uploadFileAPI(file).then(
          ({ data: { fullUrl } }) => {
            // 替换 op 中的 base64 URL 为服务器返回的 URL
            op.insert.image = fullUrl;
          }
        );

        imageUploadPromises.push(uploadPromise);
      }
    });
    assDeltaContent.ops.forEach((op) => {
      if (op.insert && op.insert.image && op.insert.image.startsWith("data:")) {
        const localSrc = op.insert.image;

        // 如果是 base64 图片，上传并替换为 URL
        const file = dataURLtoFile(localSrc, "image.png"); // 将 base64 转换为 File 对象

        const uploadPromise = uploadFileAPI(file).then(
          ({ data: { fullUrl } }) => {
            // 替换 op 中的 base64 URL 为服务器返回的 URL
            op.insert.image = fullUrl;
          }
        );

        imageUploadPromises.push(uploadPromise);
      }
    });
    try {
      await Promise.all(imageUploadPromises);
      if (currentSopData.value.sopId) {
        // 编辑保存逻辑
        const params = { ...sopState.sopModalForm };
        params.finishStandard = JSON.stringify(finishDeltaContent);
        params.assessorStandard = JSON.stringify(assDeltaContent);
        params.flowChart = JSON.stringify({
          lineList: edges.value,
          chartList: nodes.value,
        });
        put("/solution-ppm-work-order/api/v1/work_order_sop", params)
          .then((data) => {
            if (data.code !== 200) {
              message.error(data.message);
              return;
            }
            message.success(data.message);
            sopState.sopModalVisible = false;
            resetForm();
            getSopData();
          })
          .finally(() => {
            sopState.monitorSceneList = [];
            sopState.okLoading = false;
          });
      } else {
        // 新建保存逻辑
        const params = { ...sopState.sopModalForm };
        params.finishStandard = JSON.stringify(finishDeltaContent);
        params.assessorStandard = JSON.stringify(assDeltaContent);
        params.flowChart = JSON.stringify({
          lineList: edges.value,
          chartList: nodes.value,
        });
        post("/solution-ppm-work-order/api/v1/work_order_sop", params)
          .then((data) => {
            if (data.code !== 200) {
              message.error(data.message);
              return;
            }
            message.success(data.message);
            sopState.sopModalVisible = false;
            resetForm();
            getSopData();
          })
          .finally(() => {
            sopState.monitorSceneList = [];
            sopState.okLoading = false;
          });
      }
    } catch (error) {
      sopState.okLoading = false;
      console.error("图片上传失败", error);
      return; // 终止提交
    }
  });
}
function validateFlowChart(nodes, edges) {
  const nodeIdSet = new Set(nodes.map((n) => n.id));
  const connectedNodeIds = new Set();

  edges.forEach((edge) => {
    if (edge.source) connectedNodeIds.add(edge.source);
    if (edge.target) connectedNodeIds.add(edge.target);
  });

  // 找出未连接的节点
  const disconnectedNodes = nodes.filter(
    (node) => !connectedNodeIds.has(node.id)
  );

  if (disconnectedNodes.length > 0) {
    console.warn("存在未连接的节点", disconnectedNodes);
    return false;
  }

  return true;
}

// 编辑反显
function handlerEditSop(id, isDetail) {
  get(`/solution-ppm-work-order/api/v1/work_order_sop/${id}`).then(
    async (data) => {
      if (data.code !== 200) {
        message.error(data.message);
        return;
      }
      sopState.sopModalVisible = true;
      isShowForm.value = false;
      sopState.isDetail = isDetail ? true : false;
      currentSopData.value = data.data;
      sopState.modalTitle = isDetail ? "详情" : "编辑SOP";

      nextTick(() => {
        Object.assign(sopState.sopModalForm, data.data);
        console.log(data.data.flowChart);
        if (!data.data.flowChart) {
          nodes.value = [
            {
              id: "start-1",
              type: "start",
              data: { label: "开始" },
              position: { x: 50, y: 100 },
              sourcePosition: Position.Right,
            },
          ];
        } else {
          nodes.value = JSON.parse(data.data.flowChart).chartList.map(
            (item) => {
              return {
                ...item,
                selected: false,
              };
            }
          );
        }

        edges.value = JSON.parse(data.data.flowChart).lineList.map((item) => {
          return {
            ...item,
            selected: false,
          };
        });
        getMonitorSceneList();
        let assessorContent = data.data.assessorStandard;
        let finishContent = data.data.finishStandard;

        try {
          assessorContent = assessorContent
            ? JSON.parse(assessorContent)
            : { ops: [] };
        } catch (error) {
          console.warn("assessorStandard 解析失败:", error);
          assessorContent = { ops: [] };
        }

        try {
          finishContent = finishContent
            ? JSON.parse(finishContent)
            : { ops: [] };
        } catch (error) {
          console.warn("finishStandard 解析失败:", error);
          finishContent = { ops: [] };
        }

        try {
          assQuillEditor.value?.setContents(assessorContent);
        } catch (error) {
          console.error("assQuillEditor 反显失败:", error);
        }

        try {
          finishQuillEditor.value?.setContents(finishContent);
        } catch (error) {
          console.error("finishQuillEditor 反显失败:", error);
        }
      });
    }
  );
}

// 详情反显
function handleDetail(id) {
  handlerEditSop(id, true);
  sopState.isDetail = true;
  sopState.modalTitle = "详情";
  setTimeout(() => {
    console.log(nodes.value);
  }, 3000);
}

// 删除sop
function handlerDeleteSop(id) {
  del(`/solution-ppm-work-order/api/v1/work_order_sop/${id}`).then((data) => {
    if (data.code !== 200) {
      message.error(data.message);
      return;
    }
    message.success(data.message);
    getSopData();
  });
}

//     取消弹窗
function cancelSop() {
  assQuillEditor.value?.setContents("");
  finishQuillEditor.value?.setContents("");
  sopState.sopModalVisible = false;
  resetForm();
}

function resetForm() {
  sopState.sopModalForm = {
    sopName: undefined, //sop名称
    finishStandard: undefined, //完成标准
    assessorStandard: undefined, //审核标准
    workType: undefined, //工单类型
    monitorScene: undefined, //监控场景
    belongBusiness: "工单", //所属业务
    attachmentTemplateList: [], //附件名称
  };
  sopState.monitorSceneList = [];
}

//     类型改变清空监控
function hanldeTypeChange(flag) {
  if (flag) {
    sopState.filter.monitorScene = undefined;
  } else {
    sopState.sopModalForm.monitorScene = undefined;
  }
}

//     拖拉拽
// 定制化代码
import { VueFlow, useVueFlow, Handle, Panel } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import { Position } from "@vue-flow/core";
// 确保从正确路径导入 BaseEdge
import { BaseEdge } from "@vue-flow/core"; // 注意：不是 '@vue-flow/additional-components'
import {
  getStraightPath,
  getBezierPath,
  getSmoothStepPath,
} from "@vue-flow/core";
// 🟢 1. 预配置节点列表
const presetNodes = [
  // { id: '1', type: 'start', targetPosition: Position.Bottom, data: { label: '开始' }, isStart: true },
  {
    id: "2",
    type: "input",
    sourcePosition: Position.Bottom,
    data: { label: "步骤" },
  },
  {
    id: "4",
    type: "output",
    targetPosition: Position.Top,
    data: { label: "结束" },
  },
];

// 🟢 2. 画布数据
const nodes = ref([]);
const edges = ref([]);

// 🟢 3. 节点配置弹窗
const selectedNode = ref({});
const isShowForm = ref(false);

// 节点点击 - 弹窗
const onNodeClick = (event, node) => {
  console.log(event);
  if (event.node.type === "input") {
    selectedNode.value = event.node.data;
    isShowForm.value = true;
  } else {
    selectedNode.value = {};
    isShowForm.value = false;
  }
};
const { project } = useVueFlow();
// 拖拽相关
const onDragStart = (event, node) => {
  event.dataTransfer.setData("application/vueflow", JSON.stringify(node));
  event.dataTransfer.effectAllowed = "move";
};

const onDragOver = (event) => {
  event.preventDefault();
  event.dataTransfer.dropEffect = "move";
};

const flowContainer = ref(null); // 绑定 vue-flow 的容器

const onDrop = (event) => {
  event.preventDefault();

  const nodeData = JSON.parse(
    event.dataTransfer.getData("application/vueflow")
  );

  // 获取 vue-flow 容器的 DOM 位置信息
  const bounds = flowContainer.value.getBoundingClientRect();

  // 计算相对 vue-flow 容器内部的坐标
  const x = event.clientX - bounds.left;
  const y = event.clientY - bounds.top;

  // 用 project() 转换为 vue-flow 的逻辑坐标（支持缩放/偏移）
  const position = project({ x, y });

  const newNode = {
    id: `${nodeData.type}-${Date.now().toString(36)}`,
    type: nodeData.type,
    isStart: nodeData.isStart,
    position,
    data: {
      label: nodeData.data.label,
      sopStep: undefined,
      solverRole: undefined,
    },
    targetPosition: nodeData.targetPosition,
    sourcePosition: nodeData.sourcePosition,
  };

  nodes.value.push(newNode);
};

// // 节点删除方法
function removeNode(id) {
  nodes.value = nodes.value.filter((node) => node.id !== id);
  // 同时删除相关连线
  edges.value = edges.value.filter(
    (edge) => edge.source !== id && edge.target !== id
  );
}

// const onConnect = params => {
//   console.log(params);

//     const newEdge = {
//         ...params,
//         // 确保路径、样式等数据正确,
//         type: 'custom'
//     }
//     edges.value = [...edges.value, newEdge] // 手动更新 edges 数据
// }
const onConnect = (params) => {
  if (!params.source || !params.target) return;

  edges.value = [
    ...edges.value,
    {
      id: `e${params.source}-${params.target}`,
      ...params,
      type: "default", // 推荐先用 default，custom 需注册
    },
  ];
};

// 删除连线
function removeEdge(id) {
  edges.value = edges.value.filter((edge) => edge.id !== id);
}

function getBacColor(props) {
  if (!props.data.sopStep || !props.data.solverRole) {
    return {
      backgroundColor: "#ff4d4f",
      color: "#fff",
    };
  } else {
    return {
      backgroundColor: "#fff",
      color: "#262626",
    };
  }
}

watch(
  () => [selectedNode.value.sopStep, selectedNode.value.solverRole],
  () => {
    const index = nodes.value.findIndex(
      (node) => node.id === selectedNode.value.id
    );
    if (index !== -1) {
      nodes.value[index].data.sopStep = selectedNode.value.sopStep;
      nodes.value[index].data.solverRole = selectedNode.value.solverRole;
    }
  },
  { deep: true }
);
</script>
<style lang="less" scoped>
.page_root {
  width: 100%;
  height: 100%;
  overflow: auto;
}

.page_root :deep(.@{ant-prefix}-spin-nested-loading),
.page_root :deep(.@{ant-prefix}-spin-container) {
  height: 100%;
}

.page_root :deep(.@{ant-prefix}-spin-container .@{ant-prefix}-table-wrapper) {
  display: flex;
  flex-flow: column nowrap;
  justify-content: flex-start;
}

.page_root :deep(.@{ant-prefix}-table) {
  flex: auto;
  overflow: auto;
}

.table-moreBtn_down {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  width: 100px;
}

.table-moreBtn_menu :deep(.table-moreBtn_menu-item):hover {
  background: #ebf1ff;
}

/*  表格树结构样式处理，展开按钮层级增加*/
:deep(.@{ant-prefix}-table-row-expand-icon) {
  z-index: 1;
}

/*  表格树结构 由于每个表格项采用自定义组件，使用绝对定位布局，开启树结构导致显示异常，暂时使用层级定位位置 */
:deep(
    .@{ant-prefix}-table-row-level-1
      .@{ant-prefix}-table-cell-with-append
      .cell-wrapper-left
  ) {
  left: 15px !important;
  width: calc(100% - 15px);
}

:deep(
    .@{ant-prefix}-table-row-level-2
      .@{ant-prefix}-table-cell-with-append
      .cell-wrapper-left
  ) {
  left: 30px !important;
  width: calc(100% - 30px);
}

:deep(
    .@{ant-prefix}-table-row-level-3
      .@{ant-prefix}-table-cell-with-append
      .cell-wrapper-left
  ) {
  left: 45px !important;
  width: calc(100% - 45px);
}

:deep(
    .@{ant-prefix}-table-row-level-4
      .@{ant-prefix}-table-cell-with-append
      .cell-wrapper-left
  ) {
  left: 60px !important;
  width: calc(100% - 60px);
}

/* 穿梭框改变默认样式，为根据组件样式控制左右栏宽度。 */
:deep(.@{ant-prefix}-transfer-list) {
  height: 100%;
  width: 50%;
}

/*工单sop*/
.sop-body {
  overflow: hidden;
  height: 100%;

  .sop-top-search {
    height: 64px;
    background-color: #fff;
    padding: 16px 24px;
    border-radius: 2px;
    margin-bottom: 16px;
  }

  .sop-content {
    height: 100%;
    padding: 16px 24px;
    background-color: #fff;

    .sop-title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;

      .sop-title-left {
        font-size: 16px;
        color: #262626;
        letter-spacing: 0;
        font-weight: 600;
        line-height: 16px;
        padding-left: 8px;
        border-left: 4px solid #3875ff;
        height: 16px;
      }
    }

    .sop-table-box {
      padding-bottom: 24px;
      border-radius: 2px;
      background-color: #fff;
    }

    .sop-table-box:deep(.@{ant-prefix}-table-thead) > tr > th {
      background: #3875ff;
      color: #fff;
      font-weight: 600;
      padding: 8px 16px;
      border-right: 1px solid #d9d9d9 !important;
      position: relative;
    }

    .sop-table-box :deep(.@{ant-prefix}-table-tbody) tr > td {
      font-size: 14px;
      font-family: arial, helvetica, "microsoft yahei";
      font-weight: 400;
      color: #262626;
      border-bottom: 1px solid #d9d9d9;
      padding: 8px 16px;
      word-break: break-all;
    }
  }
}

.sop-modal:deep(.ant3-form-item-label) {
  width: 90px;
}

:deep(.ql-editor) {
  min-height: 90px;
  background: #ffffff;
}

/*工单sop*/
.modal-sop-title {
  height: 16px;
  line-height: 16px;
  padding-left: 8px;
  border-left: 4px solid #3875ff;
  font-size: 16px;
  color: #262626;
  letter-spacing: 0;
  font-weight: 600;
  margin-bottom: 16px;
  display: flex;
  justify-content: space-between;
}

/* 全局容器样式 */
.flow-content {
  display: flex;
  flex-direction: column;
  height: 400px;
  overflow: auto;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  margin-bottom: 16px;
}

/* 顶部操作栏 */
.flow-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 24px;
  background-color: #fff;
  border-bottom: 1px solid #e8e8e8;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}

.flow-header-actions {
  display: flex;
  gap: 12px;
}

/* 主体容器 */
.flow-container {
  display: flex;
  flex: 1;
  overflow: hidden;
}

/* 左侧边栏 */
.sidebar {
  width: 85px;
  padding: 10px 0;
  overflow-y: auto;
  text-align: center;
  background: rgba(255, 255, 255, 0.8);
  margin: 5px;
  border: 1px solid rgba(56, 117, 255, 0.5);
  box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  margin-right: 16px;
}

.draggable-node {
  padding: 10px 12px;
  color: #262626;
  cursor: grab;
  display: flex;
  gap: 8px;
  justify-content: space-between;
  align-items: center;
}

.draggable-node:hover {
  background: rgba(56, 117, 255, 0.1);
  border-color: #dee2e6;
}

/* 画布区域 */
.flow-area {
  flex: 1;
  position: relative;
  background-color: #f8f9fa;
  overflow: hidden;
  border-radius: 4px;
}

/* 节点样式 */
.custom-node {
  position: relative;
  height: 36px;
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  color: #fff;
  border: 2px solid #e5e7eb;
}

.node-content {
  position: absolute;
  bottom: -25px;
}

/* 不同类型节点颜色 */
.vue-flow__node-start .custom-node {
  background-color: #fff;
  border: 2px solid rgba(114, 228, 204, 1);
  color: #262626;
  width: 36px;
  border-radius: 50%;
}

.vue-flow__node-input .custom-node {
  background-color: #ffffff;

  color: #262626;
}

.vue-flow__node-output .custom-node {
  background-color: #fff;
  border: 2px solid rgba(254, 112, 112, 1);
  color: #262626;
  width: 36px;
  border-radius: 50%;
}

:deep(.vue-flow__node-output) {
  padding: 0;
  width: unset;
  border: none;
  border-radius: 50%;
}

:deep(.vue-flow__node-output.selected) {
  border: none;
  box-shadow: none;
}

.sop-modal :deep(.vue-flow__node-input) {
  padding: 0;
  border: none;
  box-shadow: none;
}

.sop-modal :deep(.vue-flow__node-input.selected) {
  border: none;
}

/* 节点选中状态 */
.vue-flow__node.selected .custom-node {
}

/* 节点删除按钮 */
.node-delete-btn {
  position: absolute;
  top: 8px;
  right: -24px;
  cursor: pointer;
}

/* 节点新增下一个按钮 */
.add-next-btn {
  position: absolute;
  top: -15px;
  right: 50px;
  width: 20px;
  height: 20px;
  background: #ff4d4f;
  color: white;
  border-radius: 50%;
  text-align: center;
  line-height: 20px;
  font-size: 12px;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  opacity: 1;
  transition: opacity 0.2s ease;
}

.node-add-btn {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 20px;
  height: 20px;
  background: #ff4d4f;
  color: white;
  border-radius: 50%;
  text-align: center;
  line-height: 20px;
  font-size: 12px;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  opacity: 1;
  transition: opacity 0.2s ease;
}

/* 连线样式 */
.vue-flow__edge-path {
  stroke: #888;
  stroke-width: 2;
}

.vue-flow__edge.selected .vue-flow__edge-path {
  stroke: #1890ff;
  stroke-width: 3;
}

/* 连线删除按钮 */
.edge-delete-fo {
  pointer-events: all;
}

.edge-delete-btn {
  width: 20px;
  height: 20px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  opacity: 1;
  transition: opacity 0.2s ease;
  position: relative;
}

/* 控制面板样式 */
.vue-flow__controls {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  border-radius: 6px;
  overflow: hidden;
}

.vue-flow__controls-button {
  background-color: #fff;
  border-bottom: 1px solid #f0f0f0;
  transition: background-color 0.2s ease;
}

.vue-flow__controls-button:hover {
  background-color: #f5f5f5;
}

.vue-flow__controls-button svg {
  fill: #595959;
}

/* 背景网格 */
.vue-flow__background {
  background-color: #f5f5f5;
}

/* 模态框样式 */
.ant-modal-header {
  border-bottom: 1px solid #f0f0f0;
}

.ant-modal-title {
  color: #333;
  font-weight: 500;
}

.ant-form-item-label > label {
  color: #595959;
}

.readonly-wrapper {
  pointer-events: none;
  opacity: 0.85; /* 可选，加点透明度提示是只读 */
  cursor: not-allowed;
}

.node-line {
  width: 150px;
  height: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  box-shadow: none;
  line-height: 32px;
  border-radius: 4px;
  padding: 0 5px;
}

.lf-node-selected {
  border: 1px dashed #8a94a5;
  padding: 1px;
  background-color: transparent;
  border-radius: 4px;
  opacity: 1;
}

.node-hover-wrapper {
  position: relative;
  display: inline-block;
  padding: 2px;
}

//选中状态
.node-hover-wrapper::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  border: 1px dashed transparent;
  border-radius: 4px; /* 确保是矩形边框 */
  pointer-events: none; /* 不影响鼠标事件 */
  transition: border-color 0.2s;
}

.node-hover-active::after {
  border-color: rgba(24, 144, 255, 0.5);
}

.step-icon {
  width: 16px;
  height: 16px;
  background-color: #fff;
  border: 2px solid #d9d9d9;
  border-radius: 5px;
}

.end-icon {
  width: 16px;
  height: 16px;
  background-color: #fff;
  border: 2px solid rgba(254, 112, 112, 1);
  border-radius: 50%;
}
.vue-flow__handle {
  width: 12px;
  height: 12px;
}
</style>
